<template>
  <div
    style="width: 100%; height: 100%"
    @mousedown.stop
    @dragover.stop.prevent
    @drop.stop.prevent
    @wheel.stop.prevent
  >
    <div ref="container" class="three-container"></div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import _ from 'lodash'

const container = ref<HTMLDivElement | null>(null)

let scene: THREE.Scene
let camera: THREE.PerspectiveCamera
let renderer: THREE.WebGLRenderer
let raycaster: THREE.Raycaster
let mouse: THREE.Vector2
let marker: THREE.Mesh | null = null
let label: THREE.Sprite | null = null

const props = withDefaults(
  defineProps<{
    modelValue?: any
    otherPoints?: any[]
    monitorDevice: any
    isPreview?: boolean
    backgroundColor?: string
    pointsFontColor?: string
    pointsFontSize?: number
  }>(),
  {
    otherPoints: () => [], // 设置默认值为 []
    backgroundColor: '#fff',
    pointsFontColor: '#ffff00',
    pointsFontSize: 10
  }
)

const emit = defineEmits(['update:modelValue'])

const isThreeReady = ref(false)
let pendingMarkerData: any = null
let otherMarkers: THREE.Mesh[] = []

function parseRgba(rgbaStr: string): { color: THREE.Color; alpha: number } {
  const match = rgbaStr.match(/rgba?\((\d+), ?(\d+), ?(\d+), ?([\d.]+)?\)/)
  if (match) {
    const [, r, g, b, a = '1'] = match
    return {
      color: new THREE.Color(`rgb(${r}, ${g}, ${b})`),
      alpha: parseFloat(a)
    }
  }
  return { color: new THREE.Color(0, 0, 0), alpha: 1 }
}

/**
 * 初始化 Three.js
 */
const initThree = () => {
  if (!props.monitorDevice.devicePic) {
    return
  }
  // 场景
  scene = new THREE.Scene()
  scene.background = null

  // 相机
  camera = new THREE.PerspectiveCamera(
    45,
    container.value!.clientWidth / container.value!.clientHeight,
    0.1,
    1000
  )
  camera.position.set(0, 2, 5)

  // 渲染器
  renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
  const { color, alpha } = parseRgba(props.backgroundColor)
  renderer.setClearColor(color, alpha)
  renderer.setSize(container.value!.clientWidth, container.value!.clientHeight)
  container.value!.appendChild(renderer.domElement)

  new OrbitControls(camera, renderer.domElement)

  // 灯光
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
  scene.add(ambientLight)

  const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
  directionalLight.position.set(5, 10, 7.5)
  scene.add(directionalLight)

  // 射线
  raycaster = new THREE.Raycaster()
  mouse = new THREE.Vector2()

  // 加载模型
  const loader = new GLTFLoader()
  loader.load(
    props.monitorDevice.devicePic,
    (gltf: any) => {
      const model = gltf.scene
      scene.add(model)

      // 自动缩放模型
      model.scale.set(0.5, 0.5, 0.5)

      const box = new THREE.Box3().setFromObject(model)
      const center = new THREE.Vector3()
      box.getCenter(center)

      const size = new THREE.Vector3()
      box.getSize(size)
      const maxDim = Math.max(size.x, size.y, size.z)

      const fov = camera.fov * (Math.PI / 180)
      let cameraZ = maxDim / (2 * Math.tan(fov / 2))
      cameraZ *= 1.5 // 适当放大系数，避免模型太贴近

      camera.position.set(center.x, center.y, center.z + cameraZ)
      camera.lookAt(center)
    },
    undefined,
    (error) => {
      console.error('模型加载失败:', error)
    }
  )

  // 点击事件
  window.addEventListener('dblclick', onClick, false)

  animate()

  isThreeReady.value = true

  if (pendingMarkerData) {
    const { name, positionX, positionY, positionZ } = pendingMarkerData
    const pos = new THREE.Vector3(
      parseFloat(positionX),
      parseFloat(positionY),
      parseFloat(positionZ)
    )
    createMarker(pos, name)
    pendingMarkerData = null
  }

  createOtherMarkers()
}

/**
 * 创建/更新标注点
 */
const createMarker = (position: THREE.Vector3, labelText: string) => {
  // 创建 marker
  if (!marker) {
    const geometry = new THREE.SphereGeometry(0.05, 16, 16)
    const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
    marker = new THREE.Mesh(geometry, material)
    scene.add(marker)
  }
  marker.position.copy(position)

  const scale = 4 // 分辨率放大 4 倍，防止模糊
  const fontSize = 10 * scale
  // 创建文字 Sprite
  const createHighResTexture = (text: string) => {
    const canvas = document.createElement('canvas')
    const context = canvas.getContext('2d')!

    // 先用放大后的 fontSize 计算文字宽度
    context.font = `${fontSize}px Arial`
    const textWidth = context.measureText(text).width

    canvas.width = textWidth
    canvas.height = fontSize

    // 使用缩放因子让 canvas 绘制分辨率更高
    context.scale(scale, scale)
    context.font = `${10}px Arial` // 回到实际显示大小
    context.fillStyle = 'red'
    context.fillText(text, 0, 10)

    const texture = new THREE.CanvasTexture(canvas)
    texture.minFilter = THREE.LinearFilter
    texture.needsUpdate = true

    return texture
  }

  if (!label) {
    const texture = createHighResTexture(labelText)
    const spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent: true })
    label = new THREE.Sprite(spriteMaterial)
    scene.add(label)
  } else {
    // 更新文字
    const texture = createHighResTexture(labelText)
    label.material.map = texture
    label.material.needsUpdate = true
  }

  // 让 label 始终在 marker 上方
  // label.position.set(position.x, position.y, position.z)
  label.position.set(0, 1, 0)

  // 跟随 marker 旋转（因为 label 是场景里的 Sprite，会随 marker 一起旋转）
  marker.add(label)
}

/**
 * 点击事件处理
 */
const onClick = (event: MouseEvent) => {
  if (props.isPreview) return
  const rect = renderer.domElement.getBoundingClientRect()
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1

  raycaster.setFromCamera(mouse, camera)

  // 检测交点
  const intersects = raycaster.intersectObjects(scene.children, true)
  if (intersects.length > 0) {
    const point = intersects[0].point
    console.log('点击点坐标:', point)
    createMarker(point, props.modelValue.name)
    emit('update:modelValue', {
      ...props.modelValue, // 保留其他字段
      positionX: point.x.toFixed(4),
      positionY: point.y.toFixed(4),
      positionZ: point.z.toFixed(4)
    })
  }
}

/**
 * 渲染循环
 */
const animate = () => {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}

/**
 * 渲染其他监测点
 */
const createOtherMarkers = () => {
  otherMarkers.forEach((marker) => {
    scene.remove(marker)
  })
  otherMarkers = []

  props.otherPoints.forEach((point) => {
    const { name, positionX, positionY, positionZ } = point.data.measureData

    if (name && positionX && positionY && positionZ) {
      const geometry = new THREE.SphereGeometry(0.05, 16, 16)
      const material = new THREE.MeshBasicMaterial({
        color: new THREE.Color(props.pointsFontColor)
      })
      const markerMesh = new THREE.Mesh(geometry, material)
      markerMesh.position.set(parseFloat(positionX), parseFloat(positionY), parseFloat(positionZ))
      scene.add(markerMesh)
      otherMarkers.push(markerMesh)

      // 文字 label
      const createHighResTexture = (text: string) => {
        const scale = window.devicePixelRatio || 2 // 兼容高清屏
        // const fontSize = props.pointsFontSize * scale
        const fontSize = 100 * scale
        const canvas = document.createElement('canvas')
        const context = canvas.getContext('2d')!

        context.font = `${fontSize}px Arial`
        const textWidth = context.measureText(text).width

        canvas.width = textWidth
        canvas.height = fontSize

        // context.fillStyle = 'black'
        // context.fillRect(0, 0, canvas.width, canvas.height)

        context.scale(scale, scale)
        const lastFontSize = props.pointsFontSize * 5
        context.font = `${lastFontSize}px Arial` // 实际显示大小
        context.fillStyle = props.pointsFontColor

        const offsetX = (textWidth - (lastFontSize * textWidth) / 100) / 4
        context.fillText(text, offsetX, 100)

        const texture = new THREE.CanvasTexture(canvas)
        texture.minFilter = THREE.LinearFilter
        texture.needsUpdate = true
        return texture
      }

      const texture = createHighResTexture(name)
      const spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent: true })
      const labelSprite = new THREE.Sprite(spriteMaterial)
      labelSprite.position.set(0, 0.6, 0)
      markerMesh.add(labelSprite)
    }
  })
}

const onWindowResize = _.debounce(() => {
  if (!container.value || !camera || !renderer) return

  const width = container.value.clientWidth
  const height = container.value.clientHeight

  camera.aspect = width / height
  camera.updateProjectionMatrix()
  renderer.setSize(width, height)
}, 200)

let resizeObserver: ResizeObserver

const observeContainerResize = () => {
  if (!container.value) return

  resizeObserver = new ResizeObserver(() => {
    onWindowResize()
  })

  resizeObserver.observe(container.value)
}

watch(
  () => [props.otherPoints, props.pointsFontColor, props.pointsFontSize],
  () => {
    if (isThreeReady.value) {
      createOtherMarkers()
    }
  },
  { immediate: true, deep: true }
)

watch(
  () => props.backgroundColor,
  (newColor) => {
    if (renderer) {
      const { color, alpha } = parseRgba(newColor)
      renderer.setClearColor(color, alpha)
    }
  }
)

watch(
  () => props.modelValue,
  (newVal) => {
    if (newVal) {
      const { name, positionX, positionY, positionZ } = newVal
      if (name && positionX && positionY && positionZ) {
        if (!isThreeReady.value) {
          pendingMarkerData = { name, positionX, positionY, positionZ }
        } else {
          const pos = new THREE.Vector3(
            parseFloat(positionX),
            parseFloat(positionY),
            parseFloat(positionZ)
          )
          createMarker(pos, name)
        }
      }
    }
  },
  { immediate: true, deep: true }
)

onMounted(() => {
  initThree()
  observeContainerResize()
})

onBeforeUnmount(() => {
  window.removeEventListener('dblclick', onClick)
  if (resizeObserver && container.value) {
    resizeObserver.unobserve(container.value)
  }
  if (renderer) {
    renderer.dispose()
  }
})
</script>

<style scoped>
.three-container {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>
